package jmpo.ex;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.InputMismatchException;
import java.util.NoSuchElementException;
import java.util.Scanner;

/**
 * Исключения - это особые ситуации в программе, которые не позволяют
 * вам выполнить код в той последовательности, как вы хотели.
 * Обычно говорят о normal flow или good flow, или expected flow - это
 * тот порядок работы программы, который вы ожидаете в обычном состоянии.
 * Но если в процессе работы вам не удаётся выполнить какой-то из 
 * шагов, то выпереходите в некоторый <<аварийный контекст>> или error flow.
 * В этой ситуации вам следует предпринять некоторые действия, чтобы программа
 * либо восстановилась и повторила normal flow, либо завершилась максимально 
 * прозрачным для клиента способом.
 */

/**
 * Рассмотрим пример работы программы считывающей данные из стандартного потока.
 */
final class ScannerNoTry {
    /**
     * Если ввести целое число, то программа отработает ожидаемо.
     * Если ввести не целое число, то программа завершиться и не выведет 
     * никакого сообщения.
     */
    static void run() {
        var sc = new Scanner(System.in);
        /**
         * если следующим элементов в потоке будет
         * не целое число, а, например, буква,
         * то это будет аварийной ситуацией,
         * так как .nextInt() не сможет вернуть
         * адекватного результата и не повредить 
         * воходной поток.
         */
        var i = sc.nextInt();
        System.out.format("The next number is: %d\n", i + 1);
    }
}

/**
 * В Java для работы с аварийным контекстом используется механиз try / catch 
 * блоков.
 * В блоке try описывается ваш ожидаемый порядок исполнения.
 * Ниже можно указать некоторое количество блоков catch.
 * Ниже представлен пример с блоком try.
 */
final class ScannerTry {
    /**
     * Если ввести целое число, то программа отработает ожидаемо.
     * Если ввести не целое число, то программа завершиться и не выведет 
     * никакого сообщения.
     */
    static void run() {
        var sc = new Scanner(System.in);
        try {
            var i = sc.nextInt();
            System.out.format("The next number is: %d\n", i + 1);
        } catch(Exception e) {
            System.err.println("Something has gone a wrong way");
        }
        /**
         * Если что-то произойдёт в блоке try, 
         * то программа перейдёт в аварийный контекст
         * в блок catch, выполнит описанный там код,
         * затем продолжит исполнение далее до завершения.
         */
    }
}

/**
 * Блоки catch могут ловить разные исключения в зависимости от их типов.
 * Каждому типу исключения можно сопоставить свой блок catch.
 * Ниже представлен пример для Scanner
 */
final class ScannerMultipleCatch {
    static void run() {
        var sc = new Scanner(System.in);
        try {
            var i = sc.nextInt();
            System.out.format("The next number is: %d\n", i + 1); 
        } catch (InputMismatchException e) { // есть данные, но не соответствуют ожидаемому типу
            System.out.println("You supposed to enter the integer number");
        } catch(NoSuchElementException e) { // поток уже исчерпан, но мы продолжаем запросы
            System.err.println("The unexpected end of a stream");
        } catch(IllegalStateException e) {  // Scanner уже закрыл поток и обращение к нему невозможно
            System.err.println("The scanner has already been closed");
        }
    }
}

/**
 * В системе исключений есть иерархия:
 * Throwable - базовый класс всех исключительных ситуаций в Java. 
 * Только потомки Throable могут быть исключениями.
 * Error -> Throwable - ошибки JVM эти ошибки невозможно исправить и 
 * не стоит перехватыват, хотя это и не запрещено.
 * Exception -> Throwable - это все библиотечные и пользовательские 
 * исключения, которые связаны с кодом на Java. 
 * В Java существует понятие checked исключений - это такие ошибки,
 * которые обязательно должны быть проверены. Обычно к ним относятся 
 * ситуации, работу которых невозможно гарантировать аналитически.
 * Они являются прямыми потомками Exception.
 * RuntimeException -> Exception - это специальные исключения, которые 
 * не нужно обязательно проверять в коде. Обычно к ним относятся исключения,
 * для которых можно аналитически гарантировать их отсутствие.
 * Например, для Scanner можно провести предварительную проверку .hasNextInt(),
 * если проверка успешна, то и дальнейший вызов будет успешным (хотя и нельзя
 * гарантировать, что поток останется доступным, но это будет исключение уровня 
 * Error).
 * !!!Обратим внимание, что класс Exception пораждает checked исключение!!!
 * !!!Современная рекомендация - не создавать новых checked исключений!!!
 * 
 * Чаще всего мы сталкиваемся с checked исключениями при работе с файлами.
 */
final class IOExceptionExample {
    /**
     * Исключение java.io.FileNotFoundException является checked исключением.
     * Его невозможно игнорировать. Либо нужно явно указать в сигнатуре метода,
     * что исключением возникнет, либо перехватить его и обработать.
     */
    static void run() throws FileNotFoundException {
        var file = new File("not_existing_file.txt");
        var stream = new FileInputStream(file);
        var sc = new Scanner(stream);
        var i = sc.nextInt();
        System.out.format("The next number is: %d", i + 1);
    }
}

/**
 * Одна из важных особенностей catch блоков - возможность перехватывать 
 * полиморфные исключения. Так блок catch(Throwable t) обеспечит аварийный 
 * контекст для любого возможного исключения, включая Error.
 * Блок catch(RuntimeException re) обеспечит аварийный контекст для любого
 * непроверяемого исключения, а блок catch(IOException ioe) - любого 
 * исключения из пакета классов java.io.
 */
final class IOExceptionCatch {
    static void run() {
        try {
            var file = new File("not_existing_file.txt");
            var stream = new FileInputStream(file);
            var sc = new Scanner(stream);
            var i = sc.nextInt();
            System.out.format("The next number is: %d", i + 1);
        }
        /**
         * так как мы обработали исключение IOException, и 
         * иключение FileNotFoundException является его потомком,
         * то мы обработали и FileNotFoundException;
         * больше его не нужно указывать в сигнатуре метода
         */ 
        catch(IOException ioe) {
            System.err.println("Something has gone wrong with I/O");
        }
        /*catch(FileNotFoundException e) {
            // данный блок кода никогда не будет исполнен,
            // так как все исключения этого типа будут перехвачены
            // блоком выше
            // порядок блоков catch имеет значение!
        }*/
    }
}

/**
 * Если мы хотим перехватить не все исключения, которые наследуют некоторому 
 * общему предку, то мы можем указать типы этих исключений 
 * через знак | (вертикальная черта). 
 * catch(EOFException | FileNotFoundException e)
 * В этом случае мы можем выполнить общий для обоих типов исключения код.
 * !!!Наиболее глупый поступок - это сначала перехватить несколько типов 
 * исключений, а затем выполнить типозависимый код через интроспекцию!!!
 */

/**
 * У класса Throwable есть несколько важных диагностических методов:
 *     String getMessage() - получить сообщение, переданное при конструировании 
 *     Throwable getCause() - возвращает исключение, которое было преобразованно в настоящее
 *     StackTraceElement[] getStackTrace() - возвращает массив вызовов, которые свернулись при возникновении исключения
 */
final class ExceptionInformation {
    static void run() {
        try {
            var file = new File("not_existing_file.txt");
            var stream = new FileInputStream(file);
            var sc = new Scanner(stream);
            var i = sc.nextInt();
            System.out.format("The next number is: %d", i + 1);
        } catch(IOException e) {
            System.out.format("Msg: %s, Cause: %s\n", e.getMessage(), e.getCause());
            for (var element : e.getStackTrace()) {
                System.out.println(element.getMethodName());
            }
        }
    }
}

public class ExceptionExample {
    public static void main(String[] args) throws FileNotFoundException {
        if (false) ScannerNoTry.run();
        if (false) ScannerTry.run();
        if (false) ScannerMultipleCatch.run();
        if (false) IOExceptionExample.run();
        if (false) IOExceptionCatch.run();
        if (true) ExceptionInformation.run();
    }
}
